package lowijs.XfDroid;

import static lowijs.XfDroid.XfDroidConstants.ARG_0;
import static lowijs.XfDroid.XfDroidConstants.DOCK_EVENT;
import static lowijs.XfDroid.XfDroidConstants.DOCK_STATE;
import static lowijs.XfDroid.XfDroidConstants.EMPTY_STRING;
import static lowijs.XfDroid.XfDroidConstants.NOT_CONNECTED_EXCEPTION;
import static lowijs.XfDroid.XfDroidConstants.PASSWORD;
import static lowijs.XfDroid.XfDroidConstants.PREF_SEND_NOTIFICATIONS;
import static lowijs.XfDroid.XfDroidConstants.USERNAME;

import com.googlecode.xfdroid.R;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import lowijs.XfireClient.IM;
import lowijs.XfireClient.XfireClient;
import lowijs.XfireClient.XfireObserver;
import lowijs.XfireClient.event.DatalessEvent;
import lowijs.XfireClient.event.EventManager;
import lowijs.XfireClient.event.FriendEvent;
import lowijs.XfireClient.event.MessageEvent;
import lowijs.XfireClient.event.ReceiveImEvent;
import lowijs.XfireClient.event.XfireEvent;
import lowijs.XfireClient.packet.RequestInfoPacket;
import lowijs.XfireClient.packet.SendImPacket;
import lowijs.util.logging.Logging;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;

public class XfDroidService extends Service implements XfireObserver {
	private static final int NM_NONE = 0;

	private static final int NM_ALL = 1;

	private static final int NM_OTHER = 2;

	private static XfDroidService INSTANCE;

	private XfireClient xfc;

	private int notificationMode = NM_ALL;

	private long activeChannel;

	private SharedPreferences prefs;

	private NotificationController notification;

	private Executor executor;

	private boolean screenOff;

	private boolean docked;

	private ScreenReceiver screenReciever;

	private DockReceiver dockReciever;

	private boolean online;

	//private PowerManager pm;

	//private WakeLock wl;

	class ScreenReceiver extends BroadcastReceiver {
		@Override
		public void onReceive(Context context, Intent intent) {
			if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
				setScreenOff(true);
			} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
				setScreenOff(false);
			}
		}
	}

	class DockReceiver extends BroadcastReceiver {
		@Override
		public void onReceive(Context context, Intent intent) {
			int code = intent.getExtras().getInt(DOCK_STATE);

			if (code == 1 || code == 2) {
				setDocked(true);
			} else {
				setDocked(false);
			}
		}
	}

	public void setOnline(boolean online) {
		if (this.online != online) {
			this.online = online;

			//getNotification().notifyAppStatus(this, online);
			if (online) {
				String title = getString(R.string.xfdroid_connected);
				String text = getString(R.string.xfdroid_connected_long);
				Notification application = new Notification(R.drawable.xfire_online, title, System.currentTimeMillis());
				application.flags = online ? Notification.FLAG_ONGOING_EVENT : 0;
				application.defaults = 0;

				Intent fi = new Intent(this, Friendslist.class);
				fi.setFlags(Intent.FLAG_FROM_BACKGROUND | Intent.FLAG_ACTIVITY_SINGLE_TOP);
				PendingIntent pi = PendingIntent.getActivity(this, 0, fi, 0);
				application.setLatestEventInfo(this, title, text, pi);
				startForegroundCompat(R.drawable.xfire_online, application);
			} else {
				stopForegroundCompat(R.drawable.xfire_online);
			}
		}
	}

	public void onCreate() {
		super.onCreate();
	    ErrorHandler.install();

		mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
	    try {
	        mStartForeground = getClass().getMethod("startForeground", mStartForegroundSignature);
	        mStopForeground = getClass().getMethod("stopForeground", mStopForegroundSignature);
	    } catch (NoSuchMethodException e) {
	        // Running on an older platform.
	        mStartForeground = mStopForeground = null;
	    }

		//pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
		//wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getSimpleName());

		EventManager.addObserver(this);
		INSTANCE = this;

		online = false;

		if (xfc == null) {
			xfc = new XfireClient(this);
		}

		Logging.getLogging().serviceCreated();

		IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
		filter.addAction(Intent.ACTION_SCREEN_OFF);
		screenReciever = new ScreenReceiver();
		registerReceiver(screenReciever, filter);

		try {
			filter = new IntentFilter(DOCK_EVENT);
			dockReciever = new DockReceiver();
			registerReceiver(dockReciever, filter);
		} catch (Exception e) {
		}
	}
	
	public void setScreenOff(boolean b) {
		screenOff = b;
	}
	
	public boolean isScreenOff() {
		return screenOff;
	}

	public boolean isDocked() {
		return docked;
	}

	public void setDocked(boolean docked) {
		this.docked = docked;
	}

	public void onStart(Intent i, int id) {
		Logging.getLogging().serviceStarted();
		super.onStart(i, id);
		String username = i.getStringExtra(USERNAME);
		String password = i.getStringExtra(PASSWORD);

		setOnline(false);
		xfc.connect(username, password);
	}
		
	public void onDestroy() {
		Logging.getLogging().serviceDestroyed();
		super.onDestroy();
		INSTANCE = null;
		EventManager.removeObserver(this);

		if (xfc != null) {
			xfc.disconnect(false);
		}

		unregisterReceiver(screenReciever);

		try {
			unregisterReceiver(dockReciever);
		} catch (Exception e) {
		}

		NotificationController.clearAll();
		setOnline(false);
	}

	/**
	 * Get the notification manager. If it is null, load it. There was a null
	 * pointer in the handleEvent that may have been to a null ntfMngr, this
	 * shuold insure it doesn't hapen.
	 * @return
	 */
	public NotificationController getNotification() {
		if (notification == null) {
			notification = NotificationController.getInstance(getBaseContext());
		}

		return notification;
	}

	/*
	 * Package only
	 */
	XfireClient getXfireClient() {
		return xfc;
	}

	public static void sendAllNotifications() {
		if (INSTANCE != null) {
			INSTANCE.notificationMode = NM_ALL;
		}
	}

	public static void sendNoNotifications() {
		if (INSTANCE != null) {
			INSTANCE.notificationMode = NM_NONE;
		}
	}

	public static void sendNoNotificationsFor(long channel) {
		if (INSTANCE != null) {
			INSTANCE.notificationMode = NM_OTHER;
			INSTANCE.activeChannel = channel;
		}
	}

	public void handleEvent(XfireEvent event) {
		Logging.getLogging().serviceHandleEvent(event.getEventType());

		switch(event.getEventType()) {
		case XfireEvent.XF_RECVIM: {
			ReceiveImEvent e = (ReceiveImEvent)event;
			IM im = e.getIm();

			if (notificationMode == NM_NONE || activeChannel == im.getChannelid()) {
				return;
			}

			postNotification(im);
			return;
		}
		/*
		case XfireEvent.XF_SENDIM: {
			SendImEvent sie = (SendImEvent)event;
			Friend f = getFrndMngr().getFriendBySid(sie.getSid());

			if (f != null) {
				getNotification().cancelUserNotifications(f.getUseridAsInt());
			}

			SendImPacket sip = new SendImPacket(sie.getSid(), sie.getImindex(), sie.getMessage());
			xfc.send(sip);

			return;
		}
		 */
		case XfireEvent.XF_REQINFO: {
			long[] friends = ((FriendEvent)event).getFriends();
			FriendDBAdapter db = FriendDBAdapter.getInstance(getBaseContext());

			for (long f : friends) {
				xfc.send(new RequestInfoPacket(db.userid(f)));
			}

			return;
		}
		}
	}
	
	private void postNotification(IM im) {
		if (!getPrefs().getBoolean(PREF_SEND_NOTIFICATIONS, true)) {
			return;
		}

		long channelid = im.getChannelid();

		if (channelid < 1) {
			Logging.getLogging().badChannel(im);
			return;
		}

		String ticker = getString(R.string.notif_message_from).replace(ARG_0, im.getUser());
		getNotification().notifyChannel(this, channelid, ticker, im.getUser(), im.getMessage());
	}

	/*
	 * Unused.
	 */
	public IBinder onBind(Intent intent) {
		return null;
	}

	public static int getReconnectTime() {
		if (INSTANCE != null && INSTANCE.xfc != null) {
			return INSTANCE.xfc.getReconnectTime();
		}

		return 0;
	}

	public static void stopReconnect() {
		if (INSTANCE != null && INSTANCE.xfc != null) {
			INSTANCE.xfc.stopReconnect();
		}
	}

	public static boolean isReconnecting() {
		if (INSTANCE != null && INSTANCE.xfc != null) {
			return INSTANCE.xfc.isReconnecting();
		}

		return false;
	}

	public static boolean isConnected() {
		if (INSTANCE == null) {
			Logging.getLogging().serviceNotRunning();
			return false;
		} else if (INSTANCE.xfc == null) {
			Logging.getLogging().serviceHasNoClientRunner();
			return false;
		}

		return INSTANCE.xfc.isConnected();
	}

	public void loginFailed(IOException e) {
		ErrorHandler.write(e);
		EventManager.fireEvent(new DatalessEvent(XfireEvent.XF_LOGINFAIL));
	}

	public static void logoff() {
		if (INSTANCE != null) {
			Logging.getLogging().disconnectingClient();
			INSTANCE.xfc.disconnect(false);
			Logging.getLogging().stoppingService();
			INSTANCE.stopSelf();
		}
	}

	public void disconnected(boolean temporary) {
		setOnline(false);

		if (temporary) {
			Logging.getLogging().serviceDC();
			EventManager.fireEvent(new DatalessEvent(XfireEvent.XF_LOST_CONNECTION));
		} else {
			Logging.getLogging().serviceLogoff();
			EventManager.fireEvent(new DatalessEvent(XfireEvent.XF_OFFLINE));
		}
	}

	public SharedPreferences getPrefs() {
		if (prefs == null) {
			prefs = PreferenceManager.getDefaultSharedPreferences(this);
		}

		return prefs;
	}

	public Executor getExecutor() {
		if (executor == null) {
			executor = Executors.newCachedThreadPool();
		}

		return executor;
	}

	public void loginFailed() {
		xfc.close();
		EventManager.fireEvent(new DatalessEvent(XfireEvent.XF_LOGINFAIL));
	}

	public static String getStatusText() {
		if (INSTANCE != null && INSTANCE.xfc != null) {
			return INSTANCE.xfc.getStatusText();
		}

		return null;
	}

	public static String getUsername() {
		if (INSTANCE != null && INSTANCE.xfc != null) {
			return INSTANCE.xfc.getUsername();
		}

		return EMPTY_STRING;
	}

	public static void setStatusText(String string) {
		if (INSTANCE != null && INSTANCE.xfc != null) {
			INSTANCE.xfc.setStatusText(string);
		}
	}

	public static void setNickname(String string) {
		if (INSTANCE != null && INSTANCE.xfc != null) {
			INSTANCE.xfc.setNickname(string);
		}
	}

	public boolean reconnect() {
		if (xfc.reconnect()) {
			Logging.getLogging().serviceReconencted();
			return true;
		}

		Logging.getLogging().serviceReconenctFailed();
		return false;
	}

	public void clientStarting() {
		Logging.getLogging().serviceStarting(xfc);
		getExecutor().execute(xfc);
	}

	public void clientClosed(int event, int messageid) {
		Logging.getLogging().serviceStopping(xfc);

		if (event > 0 && messageid > 0) {
			EventManager.fireEvent(new MessageEvent(event, messageid));
		} else if (event > 0) {
			EventManager.fireEvent(new DatalessEvent(event));
		}

		// Reconnector failed, terminate service
		Logging.getLogging().serviceStopSelf();
		stopSelf();
	}

	public static boolean sendIM(long channelid, String message) throws IOException {
		if (!isConnected()) {
			throw new IOException(NOT_CONNECTED_EXCEPTION);
		}

		IM im = FriendDBAdapter.getInstance(null).postMessage(channelid, message);

		if (im != null) {
			Log.d(XfDroidConstants.LOG_TAG, FriendDBAdapter.toString(im.getSid()) + ": Message sent " + message + ": " + im.getImindex());
			SendImPacket packet = new SendImPacket(im.getSid(), im.getImindex(), message);
			INSTANCE.xfc.send(packet);
			return true;
		} else {
			Log.d(XfDroidConstants.LOG_TAG, "Could not create im object, failed to send to channel " + channelid);
		}

		return false;
	}

	public void notify(String[] results) {
		getNotification().notifyAll(XfDroidService.this, results);
	}

	private static final Class<?>[] mStartForegroundSignature = new Class<?>[] {int.class, Notification.class};

	private static final Class<?>[] mStopForegroundSignature = new Class<?>[] {boolean.class};

	private NotificationManager mNM;

	private Method mStartForeground;

	private Method mStopForeground;

	private Object[] mStartForegroundArgs = new Object[2];

	private Object[] mStopForegroundArgs = new Object[1];

	/**
	 * This is a wrapper around the new startForeground method, using the older
	 * APIs if it is not available.
	 */
	void startForegroundCompat(int id, Notification notification) {
	    // If we have the new startForeground API, then use it.
	    if (mStartForeground != null) {
	        mStartForegroundArgs[0] = Integer.valueOf(id);
	        mStartForegroundArgs[1] = notification;
	        try {
	            mStartForeground.invoke(this, mStartForegroundArgs);
	        } catch (InvocationTargetException e) {
	            // Should not happen.
	            Log.w(XfDroidConstants.LOG_TAG, "Unable to invoke startForeground", e);
	        } catch (IllegalAccessException e) {
	            // Should not happen.
	            Log.w(XfDroidConstants.LOG_TAG, "Unable to invoke startForeground", e);
	        }
	        return;
	    }

	    // Fall back on the old API.
	    setForeground(true);
	    mNM.notify(id, notification);
	}

	/**
	 * This is a wrapper around the new stopForeground method, using the older
	 * APIs if it is not available.
	 */
	void stopForegroundCompat(int id) {
	    // If we have the new stopForeground API, then use it.
	    if (mStopForeground != null) {
	        mStopForegroundArgs[0] = Boolean.TRUE;
	        try {
	            mStopForeground.invoke(this, mStopForegroundArgs);
	        } catch (InvocationTargetException e) {
	            // Should not happen.
	            Log.w(XfDroidConstants.LOG_TAG, "Unable to invoke stopForeground", e);
	        } catch (IllegalAccessException e) {
	            // Should not happen.
	            Log.w(XfDroidConstants.LOG_TAG, "Unable to invoke stopForeground", e);
	        }
	        return;
	    }

	    // Fall back on the old API.  Note to cancel BEFORE changing the
	    // foreground state, since we could be killed at that point.
	    mNM.cancel(id);
	    setForeground(false);
	}
}
